// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef GPU_COMMAND_BUFFER_CLIENT_MAPPED_MEMORY_H_
#define GPU_COMMAND_BUFFER_CLIENT_MAPPED_MEMORY_H_

#include <stddef.h>
#include <stdint.h>

#include <memory>

#include "base/bind.h"
#include "base/macros.h"
#include "base/trace_event/memory_dump_provider.h"
#include "gpu/command_buffer/client/fenced_allocator.h"
#include "gpu/command_buffer/common/buffer.h"
#include "gpu/gpu_export.h"

namespace gpu {

class CommandBufferHelper;

// Manages a shared memory segment.
class GPU_EXPORT MemoryChunk {
public:
    MemoryChunk(int32_t shm_id,
        scoped_refptr<gpu::Buffer> shm,
        CommandBufferHelper* helper);
    ~MemoryChunk();

    // Gets the size of the largest free block that is available without waiting.
    unsigned int GetLargestFreeSizeWithoutWaiting()
    {
        return allocator_.GetLargestFreeSize();
    }

    // Gets the size of the largest free block that can be allocated if the
    // caller can wait.
    unsigned int GetLargestFreeSizeWithWaiting()
    {
        return allocator_.GetLargestFreeOrPendingSize();
    }

    // Gets the size of the chunk.
    unsigned int GetSize() const
    {
        return static_cast<unsigned int>(shm_->size());
    }

    // The shared memory id for this chunk.
    int32_t shm_id() const
    {
        return shm_id_;
    }

    // Allocates a block of memory. If the buffer is out of directly available
    // memory, this function may wait until memory that was freed "pending a
    // token" can be re-used.
    //
    // Parameters:
    //   size: the size of the memory block to allocate.
    //
    // Returns:
    //   the pointer to the allocated memory block, or NULL if out of
    //   memory.
    void* Alloc(unsigned int size)
    {
        return allocator_.Alloc(size);
    }

    // Gets the offset to a memory block given the base memory and the address.
    // It translates NULL to FencedAllocator::kInvalidOffset.
    unsigned int GetOffset(void* pointer)
    {
        return allocator_.GetOffset(pointer);
    }

    // Frees a block of memory.
    //
    // Parameters:
    //   pointer: the pointer to the memory block to free.
    void Free(void* pointer)
    {
        allocator_.Free(pointer);
    }

    // Frees a block of memory, pending the passage of a token. That memory won't
    // be re-allocated until the token has passed through the command stream.
    //
    // Parameters:
    //   pointer: the pointer to the memory block to free.
    //   token: the token value to wait for before re-using the memory.
    void FreePendingToken(void* pointer, unsigned int token)
    {
        allocator_.FreePendingToken(pointer, token);
    }

    // Frees any blocks whose tokens have passed.
    void FreeUnused()
    {
        allocator_.FreeUnused();
    }

    // Gets the free size of the chunk.
    unsigned int GetFreeSize() { return allocator_.GetFreeSize(); }

    // Returns true if pointer is in the range of this block.
    bool IsInChunk(void* pointer) const
    {
        return pointer >= shm_->memory() && pointer < reinterpret_cast<const int8_t*>(shm_->memory()) + shm_->size();
    }

    // Returns true of any memory in this chunk is in use.
    bool InUse()
    {
        return allocator_.InUse();
    }

    size_t bytes_in_use() const
    {
        return allocator_.bytes_in_use();
    }

private:
    int32_t shm_id_;
    scoped_refptr<gpu::Buffer> shm_;
    FencedAllocatorWrapper allocator_;

    DISALLOW_COPY_AND_ASSIGN(MemoryChunk);
};

// Manages MemoryChunks.
class GPU_EXPORT MappedMemoryManager
    : public base::trace_event::MemoryDumpProvider {
public:
    enum MemoryLimit {
        kNoLimit = 0,
    };

    // |unused_memory_reclaim_limit|: When exceeded this causes pending memory
    // to be reclaimed before allocating more memory.
    MappedMemoryManager(CommandBufferHelper* helper,
        size_t unused_memory_reclaim_limit);

    ~MappedMemoryManager() override;

    unsigned int chunk_size_multiple() const
    {
        return chunk_size_multiple_;
    }

    void set_chunk_size_multiple(unsigned int multiple)
    {
        DCHECK(multiple % FencedAllocator::kAllocAlignment == 0);
        chunk_size_multiple_ = multiple;
    }

    size_t max_allocated_bytes() const
    {
        return max_allocated_bytes_;
    }

    void set_max_allocated_bytes(size_t max_allocated_bytes)
    {
        max_allocated_bytes_ = max_allocated_bytes;
    }

    // Allocates a block of memory
    // Parameters:
    //   size: size of memory to allocate.
    //   shm_id: pointer to variable to receive the shared memory id.
    //   shm_offset: pointer to variable to receive the shared memory offset.
    // Returns:
    //   pointer to allocated block of memory. NULL if failure.
    void* Alloc(
        unsigned int size, int32_t* shm_id, unsigned int* shm_offset);

    // Frees a block of memory.
    //
    // Parameters:
    //   pointer: the pointer to the memory block to free.
    void Free(void* pointer);

    // Frees a block of memory, pending the passage of a token. That memory won't
    // be re-allocated until the token has passed through the command stream.
    //
    // Parameters:
    //   pointer: the pointer to the memory block to free.
    //   token: the token value to wait for before re-using the memory.
    void FreePendingToken(void* pointer, int32_t token);

    // Free Any Shared memory that is not in use.
    void FreeUnused();

    // Overridden from base::trace_event::MemoryDumpProvider:
    bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
        base::trace_event::ProcessMemoryDump* pmd) override;

    // Used for testing
    size_t num_chunks() const
    {
        return chunks_.size();
    }

    size_t bytes_in_use() const
    {
        size_t bytes_in_use = 0;
        for (size_t ii = 0; ii < chunks_.size(); ++ii) {
            bytes_in_use += chunks_[ii]->bytes_in_use();
        }
        return bytes_in_use;
    }

    // Used for testing
    size_t allocated_memory() const
    {
        return allocated_memory_;
    }

private:
    typedef std::vector<std::unique_ptr<MemoryChunk>> MemoryChunkVector;

    // size a chunk is rounded up to.
    unsigned int chunk_size_multiple_;
    CommandBufferHelper* helper_;
    MemoryChunkVector chunks_;
    size_t allocated_memory_;
    size_t max_free_bytes_;
    size_t max_allocated_bytes_;
    // A process-unique ID used for disambiguating memory dumps from different
    // mapped memory manager.
    int tracing_id_;

    DISALLOW_COPY_AND_ASSIGN(MappedMemoryManager);
};

// A class that will manage the lifetime of a mapped memory allocation
class GPU_EXPORT ScopedMappedMemoryPtr {
public:
    ScopedMappedMemoryPtr(
        uint32_t size,
        CommandBufferHelper* helper,
        MappedMemoryManager* mapped_memory_manager)
        : buffer_(NULL)
        , size_(0)
        , shm_id_(0)
        , shm_offset_(0)
        , flush_after_release_(false)
        , helper_(helper)
        , mapped_memory_manager_(mapped_memory_manager)
    {
        Reset(size);
    }

    ~ScopedMappedMemoryPtr()
    {
        Release();
    }

    bool valid() const
    {
        return buffer_ != NULL;
    }

    void SetFlushAfterRelease(bool flush_after_release)
    {
        flush_after_release_ = flush_after_release;
    }

    uint32_t size() const
    {
        return size_;
    }

    int32_t shm_id() const
    {
        return shm_id_;
    }

    uint32_t offset() const
    {
        return shm_offset_;
    }

    void* address() const
    {
        return buffer_;
    }

    void Release();

    void Reset(uint32_t new_size);

private:
    void* buffer_;
    uint32_t size_;
    int32_t shm_id_;
    uint32_t shm_offset_;
    bool flush_after_release_;
    CommandBufferHelper* helper_;
    MappedMemoryManager* mapped_memory_manager_;
    DISALLOW_COPY_AND_ASSIGN(ScopedMappedMemoryPtr);
};

} // namespace gpu

#endif // GPU_COMMAND_BUFFER_CLIENT_MAPPED_MEMORY_H_
